package com.hhf.rpc.core.spi;

import cn.hutool.core.io.resource.ResourceUtil;
import com.hhf.rpc.core.serializer.Serializer;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author hhf
 * @Date 2024/10/17
 * @Description SPI加载器, 支持键值对映射
 */
@Slf4j
public class SpiLoader {

    /**
     * 存储已经加载的类: 接口名 => ( serviceName,serviceImpl )
     * 比如: com.hhf.rpc.core.serializer.Serializer => (jdk,JdkSerializer.class)
     */
    private static final Map<String, Map<String, Class<?>>> loaderMap = new ConcurrentHashMap<>();

    /**
     * 缓存,避免重复的new: 类路径 => 实例对象
     * 比如: com.hhf.rpc.core.serializer.impl.JdkSerializer => JdkSerializer.class
     */
    private static final Map<String, Object> instanceCache = new ConcurrentHashMap<>();

    /**
     * 系统SPI目录
     */
    private static final String RPC_SYSTEM_SPI_DIR = "META-INF/rpc/system/";

    /**
     * 自定义SPI目录
     */
    private static final String RPC_CUSTOM_SPI_DIR = "META-INF/rpc/custom/";

    /**
     * 扫描的路径
     */
    private static final String[] SCAN_DIR = new String[]{RPC_SYSTEM_SPI_DIR, RPC_CUSTOM_SPI_DIR};

    /**
     * 动态加载的类列表
     */
    private static final List<Class<?>> LOAD_CLASS_LIST = Arrays.asList(Serializer.class);

    /**
     * 加载所有 SPI
     */
    public static void loadAll() {
        log.info("加载所有 SPI ");
        for (Class<?> clazz : LOAD_CLASS_LIST) {
            load(clazz);
        }
    }

    /**
     * 加载指定类
     *
     * @param clazz
     */
    public static void load(Class<?> clazz) {
        String clazzName = clazz.getName();
        log.info("加载类型为{}的SPI",clazzName);
        //扫描路径,用户自定义SPI优先级高于系统SPI
        Map<String,Class<?>> keyClassMap = new HashMap<>();
        for (String scanDir : SCAN_DIR){
            List<URL> resources = ResourceUtil.getResources(scanDir + clazzName);
            //读取每个资源文件
            for (URL resource : resources){
                log.info("resource====>{}",resource);
                try {
                    InputStreamReader inputStreamReader = new InputStreamReader(resource.openStream());
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                    String line;
                    while ((line = bufferedReader.readLine()) != null){
                        String[] strArray = line.split("=");
                        if (strArray.length > 1){
                            String key = strArray[0];
                            String className = strArray[1];
                            log.info("{}类读取成功",className);
                            keyClassMap.put(key,Class.forName(className));
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("spi source load error");
                }
            }
        }
        loaderMap.put(clazzName,keyClassMap);
    }

    /**
     * 获取 tClass 类型的实例=> 从 instanceCache 取,若 instanceCache 没有,则先保存再取
     * @param tClass
     * @param key
     * @return
     * @param <T>
     */
    public static <T> T getInstance(Class<?> tClass,String key){
        String className = tClass.getName();
        Map<String, Class<?>> keyClassMap = loaderMap.get(className);
        //懒加载
        if (keyClassMap == null){
            synchronized (SpiLoader.class){
                if (keyClassMap == null){
                    log.info("懒加载{}",className);
                    load(tClass);
                }
            }
            keyClassMap = loaderMap.get(className);
        }
        if (!keyClassMap.containsKey(key)){
            throw new RuntimeException("实例获取失败");
        }
        //获取要加载的实例类型
        Class<?> implClass = keyClassMap.get(key);
        //从 instanceCache 取
        String implClassName = implClass.getName();
        // implClassName = com.hhf.rpc.core.serializer.impl.JdkSerializer
        if (!instanceCache.containsKey(implClassName)){
            try {
                instanceCache.put(implClassName,implClass.getDeclaredConstructor().newInstance());
            } catch (Exception e) {
                e.printStackTrace();
                String errMessage = String.format("%s 类实例化失败",implClassName);
                throw new RuntimeException(errMessage,e);
            }
        }
        return (T) instanceCache.get(implClassName);
    }


}
